import Node from './node';
import { getNodeKey } from './util';

export default class TreeStore {
	constructor(options) {
		this.currentNode = null;
		this.currentNodeKey = null;

		for (let option in options) {
			if (options.hasOwnProperty(option)) {
				this[option] = options[option];
			}
		}

		this.nodesMap = {};

		this.root = new Node({
			data: this.data,
			store: this
		});

		if (this.lazy && this.load) {
			const loadFn = this.load;
			loadFn(this.root, (data) => {
				this.root.doCreateChildren(data);
				this._initDefaultCheckedNodes();
			});
		} else {
			this._initDefaultCheckedNodes();
		}
	}

	filter(value) {
		const filterNodeMethod = this.filterNodeMethod;
		const traverse = function(node) {
			const childNodes = node.root ? node.root.childNodes : node.childNodes;

			childNodes.forEach((child) => {
				child.visible = filterNodeMethod.call(child, value, child.data, child);

				traverse(child);
			});

			if (!node.visible && childNodes.length) {
				let allHidden = true;

				childNodes.forEach((child) => {
					if (child.visible) allHidden = false;
				});

				if (node.root) {
					node.root.visible = allHidden === false;
				} else {
					node.visible = allHidden === false;
				}
			}

			if (node.visible && !node.isLeaf) node.expand();
		};

		traverse(this);
	}

	setData(newVal) {
		const instanceChanged = newVal !== this.root.data;
		this.root.setData(newVal);
		if (instanceChanged) {
			this._initDefaultCheckedNodes();
		}
	}

	getNode(data) {
		const key = typeof data !== 'object' ? data : getNodeKey(this.key, data);
		return this.nodesMap[key];
	}

	insertBefore(data, refData) {
		const refNode = this.getNode(refData);
		refNode.parent.insertBefore({ data }, refNode);
	}

	insertAfter(data, refData) {
		const refNode = this.getNode(refData);
		refNode.parent.insertAfter({ data }, refNode);
	}

	remove(data) {
		const node = this.getNode(data);
		if (node) {
			node.parent.removeChild(node);
		}
	}

	append(data, parentData) {
		const parentNode = parentData ? this.getNode(parentData) : this.root;

		if (parentNode) {
			parentNode.insertChild({ data });
		}
	}

	_initDefaultCheckedNodes() {
		const defaultCheckedKeys = this.defaultCheckedKeys || [];
		const nodesMap = this.nodesMap;

		defaultCheckedKeys.forEach((checkedKey) => {
			const node = nodesMap[checkedKey];

			if (node) {
				node.setChecked(true, !this.checkStrictly);
			}
		});
	}

	_initDefaultCheckedNode(node) {
		const defaultCheckedKeys = this.defaultCheckedKeys || [];

		if (defaultCheckedKeys.indexOf(node.key) !== -1) {
			node.setChecked(true, !this.checkStrictly);
		}
	}

	setDefaultCheckedKey(newVal) {
		if (newVal !== this.defaultCheckedKeys) {
			this.defaultCheckedKeys = newVal;
			this._initDefaultCheckedNodes();
		}
	}

	registerNode(node) {
		const key = this.key;
		if (!key || !node || !node.data) return;

		const nodeKey = node.key;
		if (nodeKey !== undefined) this.nodesMap[node.key] = node;
	}

	deregisterNode(node) {
		const key = this.key;
		if (!key || !node || !node.data) return;

		delete this.nodesMap[node.key];
	}

	getCheckedNodes(leafOnly = false) {
		const checkedNodes = [];
		const traverse = function(node) {
			const childNodes = node.root ? node.root.childNodes : node.childNodes;

			childNodes.forEach((child) => {
				if (!leafOnly && child.checked || leafOnly && child.isLeaf && child.checked) {
					checkedNodes.push(child.data);
				}

				traverse(child);
			});
		};

		traverse(this);

		return checkedNodes;
	}

	getCheckedKeys(leafOnly = false) {
		const key = this.key;
		const allNodes = this._getAllNodes();
		const keys = [];
		allNodes.forEach((node) => {
			if (!leafOnly || leafOnly && node.isLeaf) {
				if (node.checked) {
					keys.push((node.data || {})[key]);
				}
			}
		});
		return keys;
	}

	_getAllNodes() {
		const allNodes = [];
		const nodesMap = this.nodesMap;
		for (let nodeKey in nodesMap) {
			if (nodesMap.hasOwnProperty(nodeKey)) {
				allNodes.push(nodesMap[nodeKey]);
			}
		}

		return allNodes;
	}

	_setCheckedKeys(key, leafOnly = false, checkedKeys) {
		const allNodes = this._getAllNodes();
		allNodes.sort((a, b) => b.level - a.level);

		const keys = Object.keys(checkedKeys);
		allNodes.forEach((node) => {
			let checked = keys.indexOf(node.data[key] + '') > -1;

			if (!node.isLeaf) {
				if (!this.checkStrictly) {
					const childNodes = node.childNodes;

					let all = true;
					let none = true;

					for (let i = 0, j = childNodes.length; i < j; i++) {
						const child = childNodes[i];
						if (child.checked !== true || child.indeterminate) {
							all = false;
						}
						if (child.checked !== false || child.indeterminate) {
							none = false;
						}
					}

					if (all) {
						node.setChecked(true, !this.checkStrictly);
					} else if (!all && !none) {
						checked = checked ? true : 'half';
						node.setChecked(checked, !this.checkStrictly && checked === true);
					} else if (none) {
						node.setChecked(checked, !this.checkStrictly);
					}
				} else {
					node.setChecked(checked, false);
				}

				if (leafOnly) {
					node.setChecked(false, false);
					const traverse = function(e) {
						const childNodes = e.childNodes;

						childNodes.forEach((child) => {
							if (!child.isLeaf) {
								child.setChecked(false, false);
							}
							traverse(child);
						});
					};

					traverse(node);
				}
			} else {
				node.setChecked(checked, false);
			}
		});
	}

	setCheckedNodes(array, leafOnly = false) {
		const key = this.key;
		const checkedKeys = {};
		array.forEach((item) => {
			checkedKeys[(item || {})[key]] = true;
		});

		this._setCheckedKeys(key, leafOnly, checkedKeys);
	}

	setCheckedKeys(keys, leafOnly = false) {
		this.defaultCheckedKeys = keys;
		const key = this.key;
		const checkedKeys = {};
		keys.forEach((e) => {
			checkedKeys[e] = true;
		});

		this._setCheckedKeys(key, leafOnly, checkedKeys);
	}

	setDefaultExpandedKeys(keys) {
		keys = keys || [];
		this.defaultExpandedKeys = keys;

		keys.forEach((key) => {
			const node = this.getNode(key);
			if (node) node.expand(null, this.autoExpandParent);
		});
	}

	setChecked(data, checked, deep) {
		const node = this.getNode(data);

		if (node) {
			node.setChecked(!!checked, deep);
		}
	}

	getCurrentNode() {
		return this.currentNode;
	}

	setCurrentNode(node) {
		this.currentNode = node;
	}

	setCurrentNodeKey(key) {
		const node = this.getNode(key);
		if (node) {
			this.currentNode = node;
		}
	}
}
